pub mod mysql;
pub mod oracle;
pub mod postgres;

use super::Task;
use crate::datasource::record::IndexeaTask;
use log::info;
use sqlparser::ast::SetExpr::Select;
use sqlparser::ast::Statement::Query;
use sqlparser::ast::TableFactor;
use sqlparser::dialect::GenericDialect;
use sqlparser::parser::Parser;
use std::collections::HashMap;

const DIALECT: GenericDialect = GenericDialect {}; // or AnsiDialect
pub const TABLE_TASKS: &str = "indexea_tasks";
pub const ERR_TABLE_NOT_EXISTS: &str =
    "indexea_tasks not exists, you need run `indexer init` to initialize it.";

/// base database operations
trait IDatabase {
    /// init database
    fn initdb(&self, tasks: &HashMap<&String, &Task>) -> Result<(), String> {
        if tasks.len() > 0 {
            let exists = self.tasks_table_exists()?;
            if !exists {
                self.create_tasks_table()?;
                info!(
                    "tasks table created for [\"{}\"]",
                    tasks.iter().next().unwrap().1.datasource
                );
            }
            //check and create trigger
            for task in tasks.iter() {
                self.create_triggers(task.0, task.1)?;
                info!("create triggers for task [\"{}\"] in [\"{}\"]", task.0, task.1.datasource);
            }
        }
        Ok(())
    }

    fn cleandb(&self, tasks: &HashMap<&String, &Task>) -> Result<(), String> {
        for task in tasks.iter() {
            self.drop_triggers(task.0, task.1)?;
        }
        self.drop_tasks_table()?;
        Ok(())
    }

    /// check if table `indexea_tasks` exists
    fn tasks_table_exists(&self) -> Result<bool, String>;

    /// create `indexea_tasks` table
    fn create_tasks_table(&self) -> Result<(), String>;

    /// drop `indexea_tasks` table
    fn drop_tasks_table(&self) -> Result<(), String>;

    /// create triggers for each task
    fn create_triggers(&self, name: &String, task: &Task) -> Result<(), String>;

    /// drop triggers for each task
    fn drop_triggers(&self, name: &String, task: &Task) -> Result<(), String>;

    /// Generate record sql according to use configurations
    fn get_select_sql(&self, config: &Task, task: &IndexeaTask) -> String {
        let value = match task.ftype {
            1 => format!("{}", task.value),
            _ => format!("\"{}\"", task.value),
        };
        let sql = if config.sql.as_ref().unwrap().len() > 0 {
            //parse table alias
            let mut talias = String::new();
            let ast = Parser::parse_sql(&DIALECT, config.sql.as_ref().unwrap()).unwrap();
            if let Query(q) = &ast[0] {
                if let Select(s) = &*q.body {
                    for twj in &s.from {
                        let t = &twj.relation;
                        if let TableFactor::Table { name, alias, .. } = t {
                            for ident in &name.0 {
                                if ident.value.eq(config.table.as_ref().unwrap()) {
                                    if let Some(a) = alias {
                                        talias = a.name.value.clone();
                                    }
                                }
                            }
                        }
                    }
                }
            }
            let has_where = config.sql.as_ref().unwrap().to_ascii_lowercase().contains(" where ");
            format!(
                "{} {} {} = {}",
                config.sql.as_ref().unwrap(),
                if has_where { "AND" } else { "WHERE" },
                if talias.len() == 0 {
                    task.field.clone()
                } else {
                    format!("{}.{}", talias, task.field)
                },
                value
            )
        } else {
            format!(
                "SELECT * FROM {} WHERE {} = {}",
                config.table.as_ref().unwrap(),
                task.field,
                value
            )
        };
        //println!("{}", sql);
        sql
    }

    fn list_records_sql(&self, task: &Task, limit: u32, offset: u32) -> String {
        let tsql = task.sql.as_ref().unwrap();
        let sql = if tsql.len() > 0 {
            format!("{} LIMIT {limit} OFFSET {offset}", tsql)
        } else {
            format!(
                "SELECT * FROM {} ORDER BY {} LIMIT {limit} OFFSET {offset}",
                task.table.as_ref().unwrap(),
                task.primary.as_ref().unwrap()
            )
        };
        sql
    }
}
